knitr::opts_chunk$set(
echo = FALSE, message = FALSE, warning = FALSE
)
suppressPackageStartupMessages({
library(tidyverse)
library(lubridate)
library(scales)
})
ensure_pkg <- function(p){
if (!requireNamespace(p, quietly = TRUE)) install.packages(p, repos = "https://cloud.r-project.org")
}
ensure_pkg("highcharter")
## Registered S3 method overwritten by 'quantmod':
## method from
## as.zoo.data.frame zoo
ensure_pkg("lpSolve")
ensure_pkg("DiagrammeR")
library(highcharter)
## Highcharts (www.highcharts.com) is a Highsoft software product which is
## not free for commercial and Governmental use
library(lpSolve)
## Warning: package 'lpSolve' was built under R version 4.5.2
library(DiagrammeR)
## Warning: package 'DiagrammeR' was built under R version 4.5.2
options(highcharter.theme = hc_theme_flat())
hc_lang <- list(thousandsSep = ",", decimalPoint = ".")
# Highcharter version compatibility: some versions do not have hc_lang()
hc_lang_safe <- function(h, lang_list){
# If hc_lang exists, use it; otherwise try hc_opts(lang=); else no-op.
if (exists("hc_lang", where=asNamespace("highcharter"), inherits=FALSE)) {
return(highcharter::hc_lang(h, lang_list))
}
if (exists("hc_opts", where=asNamespace("highcharter"), inherits=FALSE)) {
return(highcharter::hc_opts(h, lang = lang_list))
}
h
}
# ---- run-from-anywhere path helper ----
get_script_dir <- function(){
cmdArgs <- commandArgs(trailingOnly = FALSE)
fileArgName <- "--file="
m <- grep(fileArgName, cmdArgs)
if (length(m) > 0) return(dirname(normalizePath(sub(fileArgName, "", cmdArgs[m]), winslash="/")))
if (!is.null(sys.frames()[[1]]$ofile)) return(dirname(normalizePath(sys.frames()[[1]]$ofile, winslash="/")))
if (requireNamespace("rstudioapi", quietly = TRUE) && rstudioapi::isAvailable()) {
p <- rstudioapi::getActiveDocumentContext()$path
if (nzchar(p)) return(dirname(normalizePath(p, winslash="/")))
}
normalizePath(getwd(), winslash="/")
}
PROJECT_ROOT <- get_script_dir()
base_dir <- file.path(PROJECT_ROOT, "companyA_synthetic")
dir.create(file.path(base_dir, "analysis_outputs_r"), showWarnings = FALSE, recursive = TRUE)
dir.create(file.path(base_dir, "optimization_outputs_r"), showWarnings = FALSE, recursive = TRUE)
COL_BEFORE <- "#2C3E50"
COL_AFTER <- "#18BC9C"
COL_WARN <- "#F39C12"
COL_BAD <- "#E74C3C"
# Helper: highcharter time series
hc_timeseries <- function(df, x, y, group=NULL, title="", ytitle="", subtitle=""){
df <- df %>% arrange({{x}})
h <- highchart() %>%
hc_chart(zoomType="x") %>%
hc_title(text = title) %>%
hc_subtitle(text = subtitle) %>%
hc_xAxis(type="datetime") %>%
hc_yAxis(title = list(text=ytitle)) %>%
hc_tooltip(shared=TRUE, crosshairs=TRUE) %>% hc_lang_safe(hc_lang)
if (is.null(group)){
h <- h %>% hc_add_series(df, "line", hcaes(x={{x}}, y={{y}}))
} else {
h <- h %>% hc_add_series_list(
df %>%
group_by({{group}}) %>%
group_map(~ list(
name = as.character((unique(.x %>% pull({{group}}))[1])),
type = "line",
data = .x %>% transmute(x=datetime_to_timestamp({{x}}), y={{y}}) %>% list_parse2()
))
)
}
h
}
datetime_to_timestamp <- function(x){
as.numeric(as.POSIXct(x)) * 1000
}
We take the Lean ideas from the outline, waste, flow, pull, and improvement mindset, then we apply it to one real-looking operation situation. We read the charts in order to define what problem is, then we connect signals together, and only after that we propose solution. In this dataset, we focus on flow stability, constraint behavior, quality loop, and inventory timing, because these are the common drivers that make operation look busy but customer still wait.
We also build a master dataset by joining the raw tables, so if someone ask data input come from where, we can send one file that contains the linked fields used in EDA. We keep both an event-level master and a weekly SKU-location master.
The demand line show variability and some spike. Demand itself is not waste, but it become risk when the system can not absorb it. In the BEFORE data, average weekly demand is about 2811 units, and peak week reach around 4836 units. Our capacity proxy is around 7001 units per week, so the real issue is not only total capacity on paper. The issue is how the flow absorb variability, how stable the release is, and whether the mix is correct.
We compare weekly finished throughput (PACK output) against weekly demand due. On average we are not always short, but the gap swing a lot. In the worst shortage week, the gap go down to about -3387 units, and we see 13 weeks with negative gap. This kind of swing is a common reason for expediting and late shipment, it also push planning to do batching behavior.
This chart separate processing time and waiting time by step. The first thing we watch is waiting, because waiting is pure waste, it is time where product not getting value. In the BEFORE data, the step with the highest average waiting is QC_INLINE, with average waiting around 61.8 minutes, and p95 waiting around 145.7 minutes. This p95 matters, because it show the ugly tail, when flow is really unstable and batch arrive together.
When we see waiting happening downstream, we usually do not blame that station immediately. Downstream waiting often mean upstream release is not stable, rework loop or batching make work arrive in wave. So the value stream chart is not only a local efficiency chart, it is a flow chart.
The heatmap show which line is close to full utilization. A line with high utilization for many weeks is a real constraint signal. When constraint has low slack, small variability turn into queue, and queue turn into waiting and WIP. That is why we can see waiting and late delivery even if the factory is not always low throughput. It is constraint variability, not just average speed.
Quality loss is not only quality topic, it is capacity topic. In the BEFORE data, average reject rate is about 2.8%, and average rework rate is about 0.7%. On peak weeks, reject can reach about 3.3%, and rework can reach about 1.1%. These losses reduce effective output, and they also disturb flow because rework create loop.
The Pareto chart help us see whether defects are systematic. In our data, the top defect is THREAD_BREAK, and it takes around 16.3% of all defects. If we fix top 5 defects, we can potentially recover around 72% of defect quantity. This is why Pareto is like a ROI curve for improvement, we fix the big driver first, not everything at once.
WIP is work trapped in the system. When WIP rise, it does not mean throughput rise. Often it mean congestion. In the BEFORE data, average WIP count is about 11.7 items in progress at mid-day, and peak reach 21. We use the WIP-by-step view to see where the congestion sit. If WIP concentrate around certain step, that is where flow is being blocked, usually near the constraint or near rework loop.
Inventory snapshot show the pain in a very direct way. In the BEFORE data, weekly backorder can go up to 1.2723^{4} units, and weekly on-hand can go up to 6810 units. The important Lean pattern is when both on-hand and backorder are high in the same period. We observe this happen in 2 week(s) in the top quartile range, so the system can build stock but still fail customer. This usually mean poor synchronization, wrong timing, wrong mix, or wrong location allocation.
Delivery lateness is the final output symptom. In the BEFORE data, about 20% of shipments are late. Among late shipments, p95 late days is around 4.2 days. This is consistent with a system that has unstable flow and rework loop, because even if total production is not terrible, timing is unstable.
At this point we already define the problem with numbers, demand variability is present, throughput gap swing, waiting and WIP show instability, quality loss steal capacity, inventory show imbalance, and shipment show customer pain. So the solution focus on flow control and removing systematic causes, not only pushing people to work faster.
We start with Lean actions around the constraint, controlling release (pull discipline, WIP limit), reducing batching, and making the constraint more stable (setup reduction, standard work). We also focus quality at source using Pareto defects, because less rework means more stable flow. After diagnosing waste and flow problems, we use a weekly LP for production planning. The LP does not replace Lean, it support it. It gives a feasible plan under capacity constraints, and it helps reduce the mismatch between what we produce and what customer need in timing.
Finally we compare BEFORE vs AFTER using the same measurement logic. We do not compare too early, because first we need to know what we fix. In the AFTER scenario, we expect waiting and WIP to be lower and more stable, reject and rework to reduce, inventory to be less imbalanced, and shipment lateness to improve. The charts below show the direction of change and make the story easy to defend in a discussion.